/*
 * Copyright 2011 The Rusted Roof, LLC.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.proteusframework.platformservice.grammar;

import org.proteusframework.platformservice.grammar.expression.*;
import org.proteusframework.platformservice.grammar.expression.StringValue;
import org.proteusframework.platformservice.grammar.expression.operators.arithmetic.*;
import org.proteusframework.platformservice.grammar.expression.operators.conditional.AndExpression;
import org.proteusframework.platformservice.grammar.expression.operators.conditional.OrExpression;
import org.proteusframework.platformservice.grammar.expression.operators.relational.*;
import org.proteusframework.platformservice.grammar.generated.TokenMgrError;
import org.proteusframework.platformservice.grammar.schema.Column;
import org.proteusframework.platformservice.persistence.api.messagebean.IMessageBeanDescriptor;
import org.proteusframework.platformservice.persistence.api.messagebean.IMessageBeanProperty;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

public final class DefaultGrammarInspector implements IExpressionVisitor, IItemsListVisitor, IGrammarInspector
{
    private List<String> propertyNames = new ArrayList<String>();

    private int paramCount = 0;

    private IMessageBeanDescriptor messageBeanDescriptor;

    private List<String> parserErrors = new ArrayList<String>();

    private List<IMessageBeanProperty> properties = new ArrayList<IMessageBeanProperty>();

    private SortedSet<IMessageBeanProperty> distinctProperties = new TreeSet<IMessageBeanProperty>();

    public DefaultGrammarInspector(final IMessageBeanDescriptor messageBeanDescriptor)
    {
        this.messageBeanDescriptor = messageBeanDescriptor;
    }

    @Override
    public void inspect(String filter) throws IllegalExpressionException
    {
        IExpressionParser expressionParser = ExpressionParserManager.createParser();
        try
        {
            IExpression expr = expressionParser.parse(new StringReader(filter));
            expr.accept(this);
        } catch (TokenMgrError error)
        {
            throw new IllegalExpressionException(error);
        }

        if (!isValidExpression())
        {
            StringBuilder err = new StringBuilder();
            for (String error : parserErrors)
            {
                err.append(error).append("\n");
            }
            throw new IllegalExpressionException(err.toString());
        }
    }

    @Override
    public SortedSet<IMessageBeanProperty> distinctProperties()
    {
        return distinctProperties;
    }

    @Override
    public List<IMessageBeanProperty> properties()
    {
        return properties;
    }

    @Override
    public boolean isValidExpression()
    {
        return (parserErrors.size() == 0);
    }

    @Override
    public IMessageBeanDescriptor getMessageBeanDescriptor()
    {
        return messageBeanDescriptor;
    }

    @Override
    public List<String> propertyNames()
    {
        return propertyNames;
    }

    @Override
    public int getParameterCount()
    {
        return paramCount;
    }

    @Override
    public List<String> errors()
    {
        return parserErrors;
    }

    private void evaluateProperty(String propertyName)
    {
        boolean foundMatch = false;

        for (IMessageBeanProperty property : messageBeanDescriptor.properties())
        {
            if (property.getId().equals(propertyName))
            {
                distinctProperties.add(property);
                properties.add(property);
                foundMatch = true;
                break;
            }
        }

        if (!foundMatch)
        {
            parserErrors.add("Unrecognized property: '" + propertyName + "'");
        }
    }

    public void visit(Addition addition)
    {
        visitBinaryExpression(addition, " + ");
    }

    public void visit(AndExpression andExpression)
    {
        visitBinaryExpression(andExpression, " AND ");
    }

    public void visit(Between between)
    {
        between.getLeftExpression().accept(this);
        between.getBetweenExpressionStart().accept(this);
        between.getBetweenExpressionEnd().accept(this);
    }

    public void visit(Division division)
    {
        visitBinaryExpression(division, " / ");
    }

    public void visit(DoubleValue doubleValue)
    {

    }

    public void visit(EqualsTo equalsTo)
    {
        visitBinaryExpression(equalsTo, " = ");
    }

    public void visit(GreaterThan greaterThan)
    {
        visitBinaryExpression(greaterThan, " > ");
    }

    public void visit(GreaterThanEquals greaterThanEquals)
    {
        visitBinaryExpression(greaterThanEquals, " >= ");
    }

    public void visit(InExpression inExpression)
    {
        inExpression.getLeftExpression().accept(this);
        inExpression.getItemsList().accept(this);
    }

    public void visit(InverseExpression inverseExpression)
    {
        inverseExpression.getExpression().accept(this);
    }

    public void visit(IsNullExpression isNullExpression)
    {
        isNullExpression.getLeftExpression().accept(this);
    }

    public void visit(JdbcParameter jdbcParameter)
    {
        paramCount++;
    }

    public void visit(LikeExpression likeExpression)
    {
        visitBinaryExpression(likeExpression, " LIKE ");
    }

    public void visit(ExistsExpression existsExpression)
    {
        existsExpression.getRightExpression().accept(this);
    }

    public void visit(LongValue longValue)
    {

    }

    public void visit(MinorThan minorThan)
    {
        visitBinaryExpression(minorThan, " < ");
    }

    public void visit(MinorThanEquals minorThanEquals)
    {
        visitBinaryExpression(minorThanEquals, " <= ");
    }

    public void visit(Multiplication multiplication)
    {
        visitBinaryExpression(multiplication, " * ");
    }

    public void visit(NotEqualsTo notEqualsTo)
    {
        visitBinaryExpression(notEqualsTo, " <> ");
    }

    public void visit(NullValue nullValue)
    {

    }

    public void visit(OrExpression orExpression)
    {
        visitBinaryExpression(orExpression, " OR ");
    }

    public void visit(Parenthesis parenthesis)
    {
        parenthesis.getExpression().accept(this);
    }

    public void visit(StringValue stringValue)
    {

    }

    public void visit(Subtraction subtraction)
    {
        visitBinaryExpression(subtraction, "-");
    }

    private void visitBinaryExpression(BinaryExpression binaryExpression, String operator)
    {
        binaryExpression.getLeftExpression().accept(this);
        binaryExpression.getRightExpression().accept(this);
    }

    public void visit(Column tableColumn)
    {
        propertyNames.add(tableColumn.getColumnName());
        evaluateProperty(tableColumn.getColumnName());
    }

    public void visit(ExpressionList expressionList)
    {
        for (Object o : expressionList.getExpressions())
        {
            IExpression expression = (IExpression) o;
            expression.accept(this);
        }
    }

    public void visit(DateValue dateValue)
    {

    }

    public void visit(TimestampValue timestampValue)
    {

    }

    public void visit(TimeValue timeValue)
    {

    }

    public void visit(Concat concat)
    {
        visitBinaryExpression(concat, " || ");
    }

    public void visit(BitwiseAnd bitwiseAnd)
    {
        visitBinaryExpression(bitwiseAnd, " & ");
    }

    public void visit(BitwiseOr bitwiseOr)
    {
        visitBinaryExpression(bitwiseOr, " | ");
    }

    public void visit(BitwiseXor bitwiseXor)
    {
        visitBinaryExpression(bitwiseXor, " ^ ");
    }
}
